﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Numerics;
using MathNet.Numerics.LinearAlgebra;
using SpiceNet.Integration;
using SpiceNet.Simulations;
using SpiceNet.Models;
using System.Diagnostics;

namespace SpiceNet.Circuits
{
    /// <summary>
    /// Description of a circuit
    /// </summary>
    public partial class Circuit
    {
        // Propositions:
        // - Add Node manager class, that keeps track of order, ground nodes, etc.
        public const double CHARGE = 1.6021918e-19;
        public const double CONSTCtoK = 273.15;
        public const double CONSTBoltz = 1.3806226e-23;
        public const double CONSTRefTemp = 300.15; // 27degC
        public const double CONSTroot2 = 1.41421356237;
        public const double CONSTvt0 = CONSTBoltz * (27.0 + CONSTCtoK) / CHARGE;
        public const double CONSTKoverQ = CONSTBoltz / CHARGE;
        public const double CONSTE = Math.E;
        public const double CONSTPI = Math.PI;

        /// <summary>
        /// Enumeration of circuit modes
        /// </summary>
        public enum Modes
        {
            // Old mode parameters
            Mode = 0x03,
            Tran = 0x01,
            Ac = 0x02,

            // ModeDC parameters
            Dc = 0x70,
            DcOp = 0x10,
            TranOp = 0x20,
            DcTranCurve = 0x40,

            // Old InitF parameters
            InitF = 0x3f00,
            InitFloat = 0x100,
            InitJct = 0x200,
            InitFix = 0x400,
            InitSmSig = 0x800,
            InitTran = 0x1000,
            InitPred = 0x2000,

            // Old NoSolv parameter
            Uic = 0x100001
        }

        /// <summary>
        /// The currently used Gmin when gmin-stepping
        /// </summary>
        public double DiagGmin = 0.0;

        /// <summary>
        /// The factor used for sources when source-stepping
        /// </summary>
        public double SrcFact = 1.0;

        /// <summary>
        /// Get the statistics for the circuit
        /// </summary>
        public Statistics Stat { get; private set; }

        /// <summary>
        /// Vt for the current temperature
        /// </summary>
        public double Vt { get; private set; }

        /// <summary>
        /// Is the current solution state a result of convergence
        /// </summary>
        public bool IsCon = false;

        /// <summary>
        /// Get the current state of the circuit
        /// </summary>
        public CircuitState State { get; private set; }

        /// <summary>
        /// Gets the current configuration of the circuit
        /// </summary>
        public CircuitConfiguration Config { get; private set; }

        /// <summary>
        /// Get the current state of the circuit (when using complex numbers)
        /// </summary>
        public CircuitStateComplex StateComplex { get; private set; }

        /// <summary>
        /// Get the current integration method
        /// </summary>
        public IntegrationMethod Integration { get; private set; }

        /// <summary>
        /// Get the class that describes nodes
        /// </summary>
        public NodeList Nodes { get; private set; }

        /// <summary>
        /// Get the current noise data (otherwise inaccessable)
        /// </summary>
        public NoiseData CurrentNoiseData { get; set; }

        /// <summary>
        /// The current analysis being executed
        /// </summary>
        public AnalysisType CurrentAnalysis;

        /// <summary>
        /// The current job being executed
        /// </summary>
        public Job CurrentJob;
        
        /// <summary>
        /// The number of states used by all devices
        /// </summary>
        public int NumStates;

        /// <summary>
        /// The mode of the circuit
        /// </summary>
        public Modes Mode;

        // Flag to indicate not to try brute force, but use gmin stepping first
        public bool HadNodeset = false;

        /// <summary>
        /// Constructor
        /// </summary>
        public Circuit()
        {
            // Initialize variables
            Config = new CircuitConfiguration();
            Stat = new Statistics();

            // As specified CKTinit in cktinit.c
            models = new Dictionary<Type, List<Model>>();
            Nodes = new NodeList();
            State = new CircuitState();
            StateComplex = new CircuitStateComplex();

            // Integration is special
            Integration = new Trapezoidal();
        }

        /// <summary>
        /// Execute a job
        /// </summary>
        /// <param name="reset"></param>
        /// <param name="inTask"></param>
        public void DoJob(bool reset, SpiceTask task)
        {
            // Start stopwatch
            Stat.TotalAnalysisTime.Start();

            // Integrationmethod is a little bit special
            Integration = new Trapezoidal();

            // Copy configurations
            task.Config.CopyTo(Config);
            task.IntegrationConfig.CopyTo(Integration.Config);

            if (reset)
            {
                Integration.Delta = 0.0;
                State.Time = 0.0;
                CurrentAnalysis = AnalysisType.DoingNone;

                // Normal reset
                Unsetup();
                Setup();
                Temperature();
            }

            // Run all analyses in the task
            foreach (Analysis anal in task.Analyses)
            {
                foreach (Job job in anal.Jobs)
                {
                    CurrentJob = job;
                    anal.Init(this);
                    if (anal.DoIc)
                        Ic();
                    anal.Func(this, reset);
                }
            }
            Stat.TotalAnalysisTime.Stop();
        }

        /// <summary>
        /// Do temp dependence and initialization
        /// </summary>
        public void Temperature()
        {
            Vt = CONSTKoverQ * Config.Temperature;
            foreach (Type t in models.Keys)
            {
                for (int i = 0; i < models[t].Count; i++)
                    models[t][i].Temperature(this);
            }
        }

        /// <summary>
        /// Initial conditions
        /// </summary>
        public void Ic()
        {
            // Clear the solution vector
            State.Solution.Clear();

            // Go over all nodes
            for (int i = 0; i < Nodes.Count; i++)
            {
                CircuitNode node = Nodes[i];
                if (node.Nodeset.Given)
                {
                    HadNodeset = true;
                    State.Solution[node.Number] = node.Nodeset;
                }
                if (node.IC.Given)
                {
                    State.Solution[node.Number] = node.IC;
                }
            }

            // Use initial conditions
            if (Mode.HasFlag(Modes.Uic))
            {
                foreach (Type t in models.Keys)
                {
                    for (int i = 0; i < models[t].Count; i++)
                        models[t][i].SetIc(this);
                }
            }
        }

        /// <summary>
        /// Accept the current solution
        /// </summary>
        public void Accept()
        {
            // Accept for all models and devices
            foreach (Type type in models.Keys)
            {
                for (int i = 0; i < models[type].Count; i++)
                    models[type][i].Accept(this);
            }

            // Save the solution
            Integration.SaveSolution(State.Solution);
        }
    }
}
